<?php

/**
 * Copyright (C) 2007 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     @license http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

$currentDir = dirname(__FILE__);
require_once($currentDir . "/utilities.php");
require_once($currentDir . "/../ext/nusoap.php");

/**
 * @author api.jmatt@gmail.com
 * @version 0.1
 */

class SensapilityService{
  
  /**
   * @access private
   * @var soapclientnusoap the nusoap object that interacts with the service
   */
  var $__soapClient;
  /**
   * @access private 
   * @var string the default namespace to use, mainly for headers
   */
  var $defaultNamespace = 'http://www.google.com/api/adsense/v2';
  /**
   * @access private
   * @var string the label for the default namespace
   */
  var $defaultNamespaceLabel;
  /**
   * @access private
   * @var object containing any error information from the last call
   */
  var $lastCallErrorObj;

  /**
   * Constructor for the SensaAPIlity service wrapper object
   * @param soapclientnusoap $soapClient the NuSOAP object that communicates 
   * with the service
   * @param array $namespaces associative array that matches namespace labels
   * to namespace URIs
   * @param array $defaultNamespaceLabel the namespace to be used with most
   * tags
   * @return none
   */
  function SensapilityService($soapClient, 
                              $namespaces, 
                              $defaultNamespaceLabel) {
    $this->__soapClient = $soapClient;
    // set the namespaces used in this request
    $keys = array_keys($namespaces);
    foreach ( $keys as $key ) {
      $this->__soapClient->namespaces[$key] = $namespaces[$key];
    }
    if ( !array_key_exists($defaultNamespaceLabel, $namespaces) ) {
      echo "Sensapility PHP Library: Default namespace should must be a key" . 
           " in the namespaces array passed to the service constructor! . " . 
           "Using the first element in the array instead.\n";
      $this->defaultNamespaceLabel = $keys[0];
    } else {
      $this->defaultNamespaceLabel = $defaultNamespaceLabel;
    }
    $lastCallErrorObject = null;
  }
  
  /**
   * Sets headers on the underlying soapclientnusoap object
   * @param string $newHeaders the headers to use with calls to this service 
   * @return none
   */
  function setHeaders($newHeaders) {
    $this->__soapClient->setHeaders($newHeaders);
  }
  
  /**
   * Formats SOAP exceptions for printing
   * @param mixed $callResults the result of a request to a SOAP service
   * @return string the pretty formatted message to print
   */
  function formatFault($callResults) {

    return "Fault occured!<br/>\n" . 'Code: ' .
           $callResults["detail"]["code"] . "<br/>\nMessage: " . 
           $this->__soapClient->faultcode . "<br/>\nDetail: " . 
           $this->__soapClient->faultstring . "<br/>\n";
  }
  
  /**
   * Formats communications error messages (eg. 404, 403)
   * @return string the pretty formatted message to print
   */
  function formatError() {
    return "Communications error!\n" . $this->__soapClient->getError() . "\n";
  }
  
  /**
   * Checks the soap call result to see if it is an error
   * @param mixed $callResults the result of a request to a SOAP service
   * @return mixed returns false if there is no error, otherwise returns the
   * error that can be printed or returned.
   */
  function checkForFailure($callResults) {
    $returnVal = false;
    // if we have an error back from the service, just use that
    // if its a transport error, display that
    if ( $this->__soapClient->fault ) {      
      $returnValue = $this->formatFault($callResults);
   } else {
      if ( $this->__soapClient->getError() ) {
        $returnVal = $this->formatError();
      } else {
        // no error occurred
        $returnValue = false;
      }
    }
    
    return $returnValue;
  }

  /**
   * @return string the current headers for this service object
   */
  function getHeaders() {
    return $this->__soapClient->getHeaders();
  }

  /**
   * Handles doing the actual SOAP call, detecting errors, saving error
   * and doing proper return.
   * @param string $callName the SOAP method to call
   * @param string $parameters the XML parameters to pass to the call
   * @param string $defaultNamespace the namespace to make the call in
   * @param boolean $hasReturn whether this call is expected to return anything
   * @param int $returnArrayDepth the depth of the return array
   * 0 - if there is no return value or the return is a string
   * 1 - if the return is an array or object
   * 2 - if the return is an array or object whose first member is an array 
   * or object
   * 3,4,5... - if the arrays or objects continue to nest
   * @param boolean $verboseModeActive whether to print out errors
   * @return mixed the result of the call or null if the call failed
   */
  function doSoapCall($callName, 
                      $parameters, 
                      $defaultNamespace,
                      $hasReturn,
                      $returnArrayDepth,
                      $verboseModeActive = true) {
    // set return value to default, which is as though an error occurred
    $returnValue = null;
    // empty any error from the last call
    $this->lastCallHadError = false;
    $this->lastCallErrorObj = null;

    $callResult = $this ->__soapClient->call($callName,
                                             $parameters,
                                             $defaultNamespace);

    $faultHappened = $this->checkForFailure($callResult);
    if ($faultHappened) {
      $this->lastCallHadError = true;
      $this->lastCallErrorObj = array('code' => $callResult['detail']['code'],
                                      'type' => $this->__soapClient->faultcode,
                                      'message' => 
                                          $this->__soapClient->faultstring);
      if ($verboseModeActive) {
        echo $faultHappened;
      }
    } else {
      // if the call doesn't return anything, return the empty string
      if ($hasReturn) {
        // if the return array is too shallow, make it deeper
        if ($returnArrayDepth > 0) {
          // if the returns is empty, just return an empty array
          if ("" != $callResult) {
            $currentDepth = $this->computeDepth($callResult);

            // will nest the array to the proper depth
            if ($currentDepth < $returnArrayDepth) {
              $returnValue = $this->deepenArray($callResult, 
                                                $returnArrayDepth 
                                                    - $currentDepth);
            } else {
              $returnValue = $callResult;
            }
          } else {
            $returnValue = array();
          }

        } else {
          $returnValue = $callResult;
        }
      } else {
        $returnValue = "";
      }
    }
    return $returnValue;
  }

  /**
   * Determine the depth of the nested arrays. Look at the first item 
   * in each array to see if it itself is an array.
   * @param mixed $target the primitive or object to inspect
   * @return int the depth of the target
   */
  function computeDepth($target) {
    // we could do this recursively for elegance, but we do it iteratively
    // for performance
    $depthCount = 0;

    $tmp = $target;
    while (is_array($tmp)) {
      $depthCount++;
      $keys = array_keys($tmp);
      $tmp = $tmp[$keys[0]];
    }
    return $depthCount;
  }

  /**
   * Nest the array by the specified amount.
   * @param mixed $tooShallow the array or object tree to deepend
   * @param int $increaseDepthBy How many levels should be added to the tree
   * @return mixed the same object nested in $increaseDepthBy levels of arrays
   */
  function deepenArray($tooShallow, $increaseDepthBy) {
    while ($increaseDepthBy > 0) {
      $tmp = array();
      $tmp[0] = $tooShallow;
      $tooShallow = $tmp;
      $increaseDepthBy--;
    }
    return $tooShallow;
  }

  /**
   * Returns the error of the last call, if there was one
   * @return mixed null if there was no error, an array with keys 'code',
   * 'type', and 'message' if there was
   */
  function getLastCallError() {
    if ($this->lastCallHadError) {
      return $this->lastCallErrorObj;
    } else {
      return null;
    }
  }
}
?>
